package cn.jingyuan.owl.utils.core.text.csv;

import cn.jingyuan.owl.utils.core.CharUtils;
import cn.jingyuan.owl.utils.core.ObjectUtils;
import cn.jingyuan.owl.utils.core.StringUtils;
import cn.jingyuan.owl.utils.core.io.IORuntimeException;
import cn.jingyuan.owl.utils.core.io.IoUtils;
import cn.jingyuan.owl.utils.core.text.StringsBuilder;

import java.io.Closeable;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * CSV 行解析器，参考：FastCSV
 */
public final class CsvParser implements Closeable, Serializable {

    private static final int DEFAULT_ROW_CAPACITY = 10;

    private final char[] buf = new char[IoUtils.DEFAULT_LARGE_BUFFER_SIZE];

    private final Reader reader;

    private final CsvReadConfig config;

    /** 当前读取字段 */
    private final StringsBuilder currentField = new StringsBuilder(512);

    /** 当前位置 */
    private int bufPos;

    /** 读取一段后数据长度 */
    private int bufLen;

    /** 拷贝开始的位置，一般为上一行的结束位置 */
    private int copyStart;

    /** 前一个特殊分界字符 */
    private int preChar = -1;

    /** 是否在引号包装内 */
    private boolean inQuotes;

    /** 标题行 */
    private CsvRow header;

    /** 当前行号 */
    private long lineNo;

    /** 第一行字段数，用于检查每行字段数是否一致 */
    private int firstLineFieldCount = -1;

    /** 最大字段数量 */
    private int maxFieldCount;

    /** 是否读取结束 */
    private boolean finished;

    /**
     * CSV 解析器
     *
     * @param reader Reader
     * @param config 配置，null 则为默认配置
     */
    public CsvParser(final Reader reader, CsvReadConfig config) {
        this.reader = Objects.requireNonNull(reader, "reader must not be null");
        this.config = ObjectUtils.defaultIfNull(config, CsvReadConfig.defaultConfig());
    }

    /**
     * 获取头部字段列表，如果 containsHeader 设置为 false 则抛出异常
     *
     * @return 头部列表
     *
     * @throws IllegalStateException 如果不解析头部或者没有调用 nextRow()方法
     */
    public List<String> getHeader() {
        if (!config.containsHeader) {
            throw new IllegalStateException("No header available - header parsing is disabled");
        }
        if (lineNo == 0) {
            throw new IllegalStateException("No header available - call nextRow() first");
        }
        return header.fields;
    }

    /**
     * 读取下一行数据
     *
     * @return CsvRow
     *
     * @throws IORuntimeException IO 读取异常
     */
    public CsvRow nextRow() throws IORuntimeException {
        long startingLineNo;
        List<String> currentFields;
        int fieldCount;
        while (!finished) {
            startingLineNo = ++lineNo;
            currentFields = readLine();
            if (null == currentFields) {
                break;
            }
            fieldCount = currentFields.size();
            // 末尾
            if (fieldCount == 0) {
                break;
            }

            // 跳过空行
            if (config.skipEmptyRows && fieldCount == 1 && currentFields.get(0).isEmpty()) {
                continue;
            }

            // 检查每行的字段数是否一致
            if (config.errorOnDifferentFieldCount) {
                if (firstLineFieldCount == -1) {
                    firstLineFieldCount = fieldCount;
                } else if (fieldCount != firstLineFieldCount) {
                    throw new IORuntimeException(String.format("Line %d has %d fields, but first line has %d fields", lineNo, fieldCount, firstLineFieldCount));
                }
            }

            // 记录最大字段数
            if (fieldCount > maxFieldCount) {
                maxFieldCount = fieldCount;
            }

            // 初始化标题
            if (config.containsHeader && null == header) {
                initHeader(currentFields);
                // 作为标题行后，此行跳过，下一行做为第一行
                continue;
            }

            return new CsvRow(startingLineNo, null == header ? null : header.headerMap, currentFields);
        }

        return null;
    }

    /**
     * 当前行做为标题行
     *
     * @param currentFields 当前行字段列表
     */
    private void initHeader(final List<String> currentFields) {
        final Map<String, Integer> localHeaderMap = new LinkedHashMap<>(currentFields.size());
        for (int i = 0; i < currentFields.size(); i++) {
            final String field = currentFields.get(i);
            if (StringUtils.isNotEmpty(field) && !localHeaderMap.containsKey(field)) {
                localHeaderMap.put(field, i);
            }
        }

        header = new CsvRow(this.lineNo, Collections.unmodifiableMap(localHeaderMap), Collections.unmodifiableList(currentFields));
    }

    /**
     * 读取一行数据
     *
     * @return 一行数据
     *
     * @throws IORuntimeException IO 异常
     */
    private List<String> readLine() throws IORuntimeException {
        final List<String> currentFields = new ArrayList<>(maxFieldCount > 0 ? maxFieldCount : DEFAULT_ROW_CAPACITY);

        final StringsBuilder localCurrentField = currentField;
        final char[] localBuf = this.buf;
        // 当前位置
        int localBufPos = bufPos;

        // 前一个特殊分界字符
        int localPreChar = preChar;

        // 拷贝起始位置
        int localCopyStart = copyStart;

        // 拷贝长度
        int copyLen = 0;

        while (true) {
            if (bufLen == localBufPos) {
                // 此 Buffer 读取结束，开始读取下一段
                if (copyLen > 0) {
                    localCurrentField.append(localBuf, localCopyStart, copyLen);
                }
                try {
                    bufLen = reader.read(localBuf);
                } catch (IOException e) {
                    throw new IORuntimeException(e);
                }

                if (bufLen < 0) {
                    // CSV 读取结束
                    finished = true;

                    if (localPreChar == config.fieldSeparator || localCurrentField.hasContent()) {
                        // 剩余部分作为一个字段
                        currentFields.add(StringUtils.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
                    }
                    break;
                }

                // 重置
                localCopyStart = localBufPos = copyLen = 0;
            }

            final char c = localBuf[localBufPos++];

            if (inQuotes) {
                // 引号内，做为内容，直到引号结束
                if (c == config.textDelimiter) {
                    // End of quoted text
                    inQuotes = false;
                } else {
                    if ((c == CharUtils.CR || c == CharUtils.LF) && localPreChar != CharUtils.CR) {
                        lineNo++;
                    }
                }
                copyLen++;
            } else {
                if (c == config.fieldSeparator) {
                    // 一个字段结束
                    if (copyLen > 0) {
                        localCurrentField.append(localBuf, localCopyStart, copyLen);
                        copyLen = 0;
                    }
                    currentFields.add(StringUtils.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
                    localCopyStart = localBufPos;
                } else if (c == config.textDelimiter) {
                    // 引号开始
                    inQuotes = true;
                    copyLen++;
                } else if (c == CharUtils.CR) {
                    if (copyLen > 0) {
                        localCurrentField.append(localBuf, localCopyStart, copyLen);
                    }
                    currentFields.add(StringUtils.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
                    localPreChar = c;
                    localCopyStart = localBufPos;
                    break;
                } else if (c == CharUtils.LF) {
                    if (localPreChar != CharUtils.CR) {
                        if (copyLen > 0) {
                            localCurrentField.append(localBuf, localCopyStart, copyLen);
                        }
                        currentFields.add(StringUtils.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
                        localPreChar = c;
                        localCopyStart = localBufPos;
                        break;
                    }
                    localCopyStart = localBufPos;
                } else {
                    copyLen++;
                }
            }

            localPreChar = c;
        }

        // restore fields
        bufPos = localBufPos;
        preChar = localPreChar;
        copyStart = localCopyStart;

        return currentFields;
    }

    @Override
    public void close() throws IOException {
        reader.close();
    }

}
